#include "cvxCornerDetect.h"



static void icvGetQuadrangleHypotheses(CvSeq* contours, std::vector<std::pair<float, int> >& quads, int class_id)
{
	const float min_aspect_ratio = 0.3f;
	const float max_aspect_ratio = 3.0f;
	const float min_box_size = 10.0f;

	for(CvSeq* seq = contours; seq != NULL; seq = seq->h_next)
	{
		CvBox2D box = cvMinAreaRect2(seq);
		float box_size = MAX(box.size.width, box.size.height);
		if(box_size < min_box_size)
		{
			continue;
		}

		float aspect_ratio = box.size.width/MAX(box.size.height, 1);
		if(aspect_ratio < min_aspect_ratio || aspect_ratio > max_aspect_ratio)
		{
			continue;
		}

		quads.push_back(std::pair<float, int>(box_size, class_id));
	}
}

static bool less_pred(const std::pair<float, int>& p1, const std::pair<float, int>& p2)
{
	return p1.first < p2.first;
}

static void countClasses(const std::vector<std::pair<float, int> >& pairs, size_t idx1, size_t idx2, std::vector<int>& counts)
{
	counts.assign(2, 0);
	for(size_t i = idx1; i != idx2; i++)
	{
		counts[pairs[i].second]++;
	}
}

#define DEBUG_WINDOWS 1

int CvxCornerDetect::cvxCheckChessboard(IplImage* src, CvSize size)
{
	if(src->nChannels > 1)
	{
		cvError(CV_BadNumChannels, "cvCheckChessboard", "supports single-channel images only", 
			__FILE__, __LINE__);
	}

	if(src->depth != 8)
	{
		cvError(CV_BadDepth, "cvCheckChessboard", "supports depth=8 images only", 
			__FILE__, __LINE__);
	}

	const int erosion_count = 1;
	const float black_level = 20.f;
	const float white_level = 130.f;
	const float black_white_gap = 70.f;

#if defined(DEBUG_WINDOWS)
	cvNamedWindow("src", 1);
	cvShowImage("src", src);
	cvWaitKey(0);
#endif //DEBUG_WINDOWS

	CvMemStorage* storage = cvCreateMemStorage();

	IplImage* white = cvCloneImage(src);
	IplImage* black = cvCloneImage(src);
	IplImage* contourImage = cvCloneImage(src);
	cvZero(contourImage);

	cvErode(white, white, NULL, erosion_count);
	cvDilate(black, black, NULL, erosion_count);
	IplImage* thresh = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);

	int result = 0;
	for(float thresh_level = black_level; thresh_level < white_level && !result; thresh_level += 20.0f)
	{
		cvThreshold(white, thresh, thresh_level + black_white_gap, 255, CV_THRESH_BINARY);

#if defined(DEBUG_WINDOWS)
		cvShowImage("white thresh", thresh);
		cvWaitKey(0);
#endif //DEBUG_WINDOWS

		CvSeq* first = 0;
		std::vector<std::pair<float, int> > quads;
		cvFindContours(thresh, storage, &first, sizeof(CvContour), CV_RETR_CCOMP); 
#if defined(DEBUG_WINDOWS)
		cvZero(contourImage);
		cvDrawContours(contourImage, first, cvScalarAll(255), cvScalarAll(128), 2);
		cvShowImage("white contour image", contourImage);
		cvWaitKey(0);
#endif //DEBUG_WINDOWS
		icvGetQuadrangleHypotheses(first, quads, 1);

		cvThreshold(black, thresh, thresh_level, 255, CV_THRESH_BINARY_INV);

#if defined(DEBUG_WINDOWS)
		cvShowImage("black thresh", thresh);
		cvWaitKey(0);
#endif //DEBUG_WINDOWS

		cvFindContours(thresh, storage, &first, sizeof(CvContour), CV_RETR_CCOMP);
#if defined(DEBUG_WINDOWS)
		cvZero(contourImage);
		cvDrawContours(contourImage, first, cvScalarAll(255), cvScalarAll(128), 2);
		cvShowImage("black contour image", contourImage);
		cvWaitKey(0);
#endif //DEBUG_WINDOWS
		icvGetQuadrangleHypotheses(first, quads, 0);

		const size_t min_quads_count = size.width*size.height/2;
		std::sort(quads.begin(), quads.end(), less_pred);

		// now check if there are many hypotheses with similar sizes
		// do this by floodfill-style algorithm
		const float size_rel_dev = 0.4f;

		for(size_t i = 0; i < quads.size(); i++)
		{
			size_t j = i + 1;
			for(; j < quads.size(); j++)
			{
				if(quads[j].first/quads[i].first > 1.0f + size_rel_dev)
				{
					break;
				}
			}

			if(j + 1 > min_quads_count + i)
			{
				// check the number of black and white squares
				std::vector<int> counts;
				countClasses(quads, i, j, counts);
				const int black_count = cvRound(ceil(size.width/2.0)*ceil(size.height/2.0));
				const int white_count = cvRound(floor(size.width/2.0)*floor(size.height/2.0));
				if(counts[0] < black_count*0.75 ||
					counts[1] < white_count*0.75)
				{
					continue;
				}
				result = 1;
			//	break;
			}
		}
	}


	cvReleaseImage(&thresh);
	cvReleaseImage(&white);
	cvReleaseImage(&black);
	cvReleaseMemStorage(&storage);

	return result;
}


